use std::sync::Mutex;

use futures::StreamExt;
use mongodb::{
    bson::{self, doc, oid::ObjectId, Document},
    options::FindOptions,
    Client, Collection,
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};

use crate::Result;

static TEMP: Lazy<Mutex<Option<Client>>> = Lazy::new(Mutex::default);
struct Collections {
    client: Client,
    gmsg: Collection<Document>,
}

static COLLECTIONS: Lazy<Collections> = Lazy::new(|| {
    let client = TEMP.lock().unwrap().take().unwrap();
    Collections {
        gmsg: client.database("rmsg").collection("gmsg"),
        client,
    }
});

pub fn set_client(client: Client) {
    *TEMP.lock().unwrap() = Some(client);
}

#[allow(dead_code)]
fn client() -> &'static Client {
    &COLLECTIONS.client
}

pub fn gmsg_coll() -> &'static Collection<Document> {
    &COLLECTIONS.gmsg
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GmsgDb {
    pub _id: ObjectId,
    pub create_time: i64,
    pub expire_time: i64,
    pub msg_type: GmsgType,
    pub msg: Document,
}

pub async fn insert(
    msg_type: GmsgType,
    msg: Document,
    expire: Option<i64>,
) -> Result<(ObjectId, i64)> {
    const DEFAULT_EXPIRE_SECOND: i64 = 14 * 24 * 3600;

    let coll = &COLLECTIONS.gmsg;
    let now = chrono::Local::now().timestamp();
    let expire_time = expire.unwrap_or(now + DEFAULT_EXPIRE_SECOND);

    let gmsg = GmsgDb {
        _id: ObjectId::new(),
        create_time: now,
        msg_type,
        msg,
        expire_time,
    };

    let doc = bson::to_document(&gmsg)?;
    let result = coll.insert_one(doc, None).await?;
    let object_id = result
        .inserted_id
        .as_object_id()
        .ok_or_else(|| {
            anyhow!(
                "insert gmsg type:{:?} msg:{:?} --- as_object_id error",
                gmsg.msg_type,
                gmsg.msg
            )
        })?
        .clone();
    Ok((object_id, expire_time))
}

pub async fn find_no_expire_messages() -> Result<Vec<GmsgDb>> {
    let coll = &COLLECTIONS.gmsg;

    let now = chrono::Local::now().timestamp();
    let find_options = FindOptions::builder().sort(doc! {"_id": 1}).build();
    let mut cursor = coll
        .find(doc! {"expire_time": {"$gt": now}}, find_options)
        .await?;

    let mut messages: Vec<GmsgDb> = Vec::new();
    while let Some(doc) = cursor.next().await {
        let doc = doc?;
        messages.push(bson::from_document(doc)?);
    }
    Ok(messages)
}

#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub enum GmsgType {
    GmsgTest,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GmsgTest {
    pub mail_msg: String,
}
